#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@CreateTime: 2020/11/05 9:45
@Author : shouke
'''

scenario_config={ #scenario_config 名称固定。不可更改
    "id": 1, #场景id
    "name": "压测场景名称",
    "target_user_count": 1,#分配的总压测用户数
    "spawn_rate": 5, # 每秒启动用户数（仅用于控制启动用户的速率
    # add 配置项的值为空字符串、0、None，则默认add值为target_user_count
    "initial_user_num":1, # 初始用户数,要求大于0，且为整数，如果配置为0，后者值不为真、或者未配置该选项，则默认取add的值
     # 注意 如果add配置项未配置、或者配置值为不为真，则自动忽略 initial_user_num，add， interval配置项的值，即梯度加压不起作用
    "add":5,  # 启动压测试时，启动Y个用户，然后add Y every interval seconds 即刚开始时启动 initial_user_num 个用户，然后每 interval 秒 增加 Y 个用户，直到达到 target_user_count
    "interval":30, # 单位秒
    "hold_load_time": 30,# 每台slave压测机维持负载运行时间(不包括用户启动耗时和结束耗时),要求大于0
    "iteration_num_each_user":1, # 每个用户迭代运行次数(每执行完一条链路算一次迭代) 如果该配置大于0，则每个用户运行该配置项指定次数，否则自动忽略该配置项
    "stop_rate": 5,#每秒停止用户数
    "host": "http://127.0.0.1:8000",#接口地址不能为空， 且必须填写正确
    #  如果同时提供了“ iteration_wait_time”，“ iteration_min_wait” 及“ iteration_max_wait”， 则优先判断iteration_wait_time
    #  iteration_min_wait和iteration_max_wait 必须配合使用， 随机生成等待时间【 iteration_min_wait,iteration_max_wait)， 且两者都不能为空
    "iteration_min_wait": "", #每个用户每运行完一轮迭代(执行完所选链路的chain部分)， 开始下一轮迭代前的最小等待时间
    "iteration_max_wait": "", #每个用户每运行完一轮迭代(执行完所选链路的chain部分)， 开始下一轮迭代前的最大等待时间
    "iteration_wait_time": 0, #每个用户每运行完一轮迭代(执行完所选链路的chain部分)， 开始下一轮迭代前的固定等待时间
    # iteration_mode 迭代模式 1-链路串行(每次迭代执行所有链路) 1-链路权重(每次迭代仅执行1条链路[按链路权重选取链路]) 2-链路随机(每次迭代仅执行一条链路[按链路权重随机选取])
    "iteration_mode":2, # 可选配置，默认为2 如果配置为1，自动忽略权重配置
    "scenario": [{
        "id": 1, #链路id
        "name": "链路1",
        "weight": 5, # 链路权重 #必填配置
        #####################关于weight配置项的更多说明##############################
        #本样例中，链路权重和为8，链路1占比为5/8，假设运行8次：
        # 如果迭代模式为“链路权重”，假设运行8次，该链路1会优先运行有3次，然后链路1和链路2之间按顺序来回切换运行
        # 如果迭代模式为“链路随机”，那么链路1被运行的概率为5/8，链路2被运行概率为3/8，注意，这个概率可能存在一定偏差
        #
        # 针对每个参与压测用户，此处teardown为可选配置，不参与中间过程(chain部分)的循环，仅在用户退出前最后执行一次
        # "teardown":[{
        #     "action": "teardown",
        #     "name": "数据清理",
        #     "method": "POST",
        #     "path": "/api/v1/order/data_clear",
        # }],
        "chain": [{
                "action": "controll_execution_once_only",
                "name": "仅运行一次控制器",
                "id": 5,#该步骤动作函数的唯一标识
                "children": [{
                        # 读取文件需要注意的地方，压测过程中，如果先减少用户数，再增加用户数，新增用户读取文件时，等价于在没有减少用户数的基础上，读取文件
                        "action": "read_csv_file_data",
                        "name": "帐号密码参数配置",
                        "fileName": "./data/argfile.txt",
                        ######################## 关于fileName的更多说明 ###############################
                        # 如果fileName为字符串，则表示所有压测节点共享同一个参数文件
                        # 如果fileName为列表，形如['./data/argfile.txt', './data/argfile.txt2', './data/argfile3.txt'],则每个压测节点，按顺序取各读取一个参数文件，如果文件数
                        # 小于压测节点数，不够分配给所有压测结点，那么未分配的压测结点，默认共享最后一个参数文件
                        #
                        "fileEncoding": "utf-8",
                        "variableNames": "username,password", #要求变量名称链路内唯一（ 不同链路中定义的变量可以重复）
                        "ignoreFirstLine": True, #是否忽略第一行 True 是 False 否
                        "delimiter": ",",#变量， 变量值之间的分隔符
                        "recyleOnEOF": True, #是否循环读取 True 循环读取 False 不循环
                        "independently_each_user": False # 是否每个用户独立 True 等价于每个用户从不同的文件副本读取各自所需要的数据， False 等价于每个用户用同一个文件指针、 文件句柄读取同一份文件
                    },
                    {
                        "action": "send_request",
                        "name": "请求登录", #可选配置，默认为None
                        "method": "POST",
                        "path": "/api/v1/login", #可以是绝对URL， 形如http: //127.0.0.1:8002/api/v1/login，也可以是相对URL,形如 /api/v1/login
                        "body": { #可选配置，默认为None
                            "account": "${username}", # 如果要替换的键值仅仅包含1个变量占位符，形如'${var_name}'，且不包含其它字符串时，替换后的键值类型取决于获取的用户变量值的类型
                            "password": "${password}",
                            "func1":"${__module_plugin_func1('参数1'|| '参数2' || key='key参数')}",
                            "func2":"${__module_plugin_func2('参数1'|| '参数2'|| 99|| key='key参数'|| key2= 'k参数2'|| key3=33|| key4 = 36)}",
                            "func3":"${__module_func1(123)}",
                            "func4":"${__module_func2()}",
                            "func5":"${__module_plugin_func2('参数1'|| '参数2'||${__module_func2()})}",
                            "func6":"${__module_plugin_func2('${username}'|| '${password}')}${__module_func2()}", # 函数参数支持变量
                            "func7":"${__module_plugin_func2(set([1,2,3]))}" # 函数参数支持python原生函数
                            ########### 插件函数使用的更多说明 ###########
                            # 1、格式：${ __函数名称() }、${ __函数名称( 函数参数 ) }
                            # #注意：
                            # 1） 函数名称以 _ 打头
                            # 2）${之间不能有空格
                            # 3) 函数名称和函数的左括号 ( 之间不能有空隔
                            #
                            # 2、函数参数之间使用 || 分隔 形如 ${ __function1( "str_value" || 123456 || 'test' )}
                            #
                            # 3、函数支持嵌套，形如：${ __function1( ${__function2()} )}
                            # 注意：但是填写时注意别嵌套自身，会死循环
                            #
                            # 4、函数参数支持变量，形如  ${ __function1( ${var_name} )}
                            #
                            # 5、函数参数如果是字符串，需要使用单引号、双引号引用 形如 ${ __function1( "str_value", ${var_name} )}，${ __function1(key="arg_value")}，${ __function1(key=\'arg_value\')}，
                            #
                            # 6、字符串替换规则：待替换的字符串，仅包含一个函数表达式，不含其它字符，则该字符串会被替换为函数返回值，如果还包含其它字符，或者包含多个函数，则该字符串替换函数表达式之前，会先转换函数返回值为字符串，然后替换这些函数表达式为转换后的函数返回值
                            #
                            # 7、函数参数支持python原生函数 形如  ${ __function1( set([1,2,3]) )}
                            #
                            # 8、关键词参数 关键词左右不用使用单、双引号 错误用法 "key"=123
                            #
                            # 9、支持自定义函数 参考 LocustPerfMeter\utils\plugin_functions目录下的代码样例
                            ############################################

                        },
                        ########### 关于 body更多使用说明 ###########
                        # 1、body用于存放位于请求体的请求参数、请求数据
                        # 2、如果未配置请求头，并且body为字典类型数据，则默认设置 Content-Type 为 application/json;charset=utf-8

                        "params":None, # 可选配置，默认为None
                        ########### 关于 params 的更多使用说明 ###########
                        # 1、用于存放位于url的参数（确切的说是url中 ? 号之后的参数）
                        # 2、支持的数据格式:json、字符串
                        # 举例：
                        # 假设发起的请求为 GET http:// 127.0.0.1/api/v1/users?name=shouke&occupation=testing
                        # 那么params可以设置为如下值
                        # {"name": "shouke", "occupation":"testing"}  #字典类型（非字符串）
                        # name=shouke&occupation=testing      #字符串类型
                        "stream": False,  #可选配置，默认为False
                        ########### 关于 stream 的更多使用说明 ###########
                        # 如果设置为true，则不会立即使用响应主体，而是通过访问响应的stream属性来使用响应主体对象。另外，将stream设置为True的副作用是，下载响应内容的时间不会计入由Locust报告的请求时间(If set to true the response body will not be consumed immediately
                        # and can instead be consumed by accessing the stream attribute on the Response object.
                        # Another side effect of setting stream to True is that the time for downloading the response content will not be accounted for in the request time that is reported by Locust)
                        # 参考上述解释及python sessions.request函数对该参数的说明，个人理解，这里stream参数如果设置为True,表示会立即下载response，否则不会
                        "headers": {
                            "Content-Type": "application/json"
                        },
                        ########### 关于 headers 的更多使用说明 ###########
                        # 1、如果headers为None、空等False值、未配置Content-Type，则
                        # a) # 如果发起的请求为get请求，且params不为空、None等False值，则默认设置 Content-Type 为 application/x-www-form-urlencoded;charset=utf-8
                        # b) 如果发起的请求为post请求，且params不为空、None等False值, 且body为空、None等False值，则默认设置 Content-Type 为 application/x-www-form-urlencoded;charset=utf-8
                        # c) 如果body为字典，则默认设置 Content-Type 为 application/json;charset=utf-8
                        # d) 其它情况，不对Content-Type请求头做任何配置
                        "auth": None, #可选配置，默认为None
                        "allow_redirects": True, #可选配置，默认为True
                        "children": [{ # 如果不配置响应断言，系统会执行默认断言 响应状态码为 200，201，301则算通过
                            "action": "assert_response",
                            "name": "请求响应断言",
                            "target": "body", # 断言目标，暂时支持body - 响应体,header - 响应头,status - 响应状态码
                            "rule": "assert_contain", #模式要匹配的规则,目前支持 assert_contain 包含  assert_equal 等于
                            "patterns": ["赖玉", "token"], # 模式 [模式1，模式2，模式3]
                            "logic":"or" # 模式之间的逻辑关系 and 、or  可不配置logic配置项，默认为and
                        }, {
                            "action": "extract_by_regexp",#正则表达式提取器
                            "name":"正则表达式提取器",
                            "refName": "userInfo", #引用名称 # 不能为空
                            "express": 'username":"(.+?)",.+token":"(.+?)",', #正则表达式
                            "target": "body", # 提取源对象
                            "template": "$1$$2$$0$", # 默认为$0$ $0$-取分组0数据（分组1到分组n按顺序拼接起来的数据） $1$-取分组1数据 $2$-取分组2数据 以此类推,如果给定索引越界
                            "matchNo": 0 # 默认为0，取分组中指定匹配索引的数据 matchNo 0开始，0表示随机，1表示取分组中第一个值 2 取第二个分组的值 以此类推， 如果越界，提取结果为空
                        }, {
                            "action": "extract_by_jmespath",#JSON提取器
                            "name":"jmespath提取器",
                            "refName": "token", #引用名称 # 不能为空
                            "express": 'data.token', #jmespath表达式 参考 https://jmespath.org/tutorial.html
                            "target": "body", # 提取源对象目标域(固定为body)
                        }]
                    }
                ]
            }, {
                "action": "send_request",
                "name": "获取菜单",
                "method": "GET",
                "path": "/api/v1/user/menus",
                "stream": False,
                "headers": None,
                "auth": None,
                "allow_redirects": True,
                "children": [{
                    "action": "assert_response",
                    "name": "请求响应断言",
                    "target": "status",
                    "rule": "assert_equal",
                    "patterns": [200]
                }]
            }, {
                "action": "exec_counter",
                "name": "counter1",
                "refName": "counter1", #要求计数器引用名称链路内唯一（ 不同链路中定义的计数器引用变量可以重复）
                "value": 1, #初始值
                "increment": 2, #增量步长值
                "maximum": 1000, #最大值
                "numberFormat": "100000", #未实现
                "independently_each_user": False # 是否每个用户独立 True 每个用户拥有自己的独立计数器 False 所有用户共享一个计数器
            }
            # 此处即chain list内部的teaardown会参与迭代执行，即不同于普通action，每轮执行过程中，不管中间运行过程是否报错，如果有配置该action，该action都会被执行
            # 需要注意的是：1）teardown action 必须作为chain list的直接元素,顺序不限 2) teardown里面不支持嵌套teardown action
           ,{
               "action": "teardown",
               "name": "数据清理",
               "method": "POST",
               "path": "/api/v1/order/data_clear",
               "stream": False,
               "auth": None,
               "allow_redirects": True
           }
        ]
    }, {
        "id": 2,
        "name": "链路2",
        "weight": 3,
        "chain": [{
            "action": "send_request",
            "name": "请求订单",
            "method": "GET",
            "path": "/api/v1/order/1",
            "stream": False,
            "headers": {
                "Content-Type": "application/x-www-form-urlencode"
            },
            "auth": None,
            "allow_redirects": True
        }]
    }]
}